package org.wisigoth.chat.client.jabber;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.Collection;

import javax.swing.event.EventListenerList;

import org.wisigoth.chat.client.ChatSession;
import org.wisigoth.chat.client.Conference;
import org.wisigoth.chat.client.ConnectionEvent;
import org.wisigoth.chat.client.ConnectionStateListener;
import org.wisigoth.chat.client.Contact;
import org.wisigoth.chat.client.DefaultPresence;
import org.wisigoth.chat.client.FriendList;
import org.wisigoth.chat.client.Message;
import org.wisigoth.chat.client.MessageConsumer;
import org.wisigoth.chat.client.Presence;
import org.wisigoth.chat.client.PresenceEvent;
import org.wisigoth.chat.client.PresenceListener;
import org.wisigoth.chat.client.Session;
import org.wisigoth.chat.client.Wisigoth;
import org.wisigoth.chat.client.WisigothUser;
import org.wisigoth.chat.client.exceptions.AuthenticationException;
import org.wisigoth.chat.client.exceptions.ConnectionException;
import org.wisigoth.chat.client.exceptions.FriendListException;
import org.wisigoth.chat.client.exceptions.MessageException;
import org.wisigoth.chat.client.exceptions.WisigothRuntimeException;
import org.wisigoth.chat.client.jabber.xml.XercesReader;
import org.wisigoth.chat.client.msg.ChatMessage;
import org.wisigoth.chat.client.msg.MimeType;

public class SessionImpl implements Session {

	private final WisigothImpl wisigoth;
	private ConnectionState connectionState;
	private FriendListImpl friendList;
	private final EventListenerList listeners = new EventListenerList();
	private Presence presence;
	private MessageDispatcher dispatcher;
	private WisigothUserImpl myUser;
	private String streamID;
	private Reader in;
	private Writer out;
	private OutPacketQueue messageQ;

	public SessionImpl(WisigothImpl wisigoth) {
		this.connectionState = ConnectionState.OFFLINE;
		this.wisigoth = wisigoth;
		try {
			this.in = this.getReader();
			this.out = this.getWriter();
		} catch (IOException e) {
			throw new WisigothRuntimeException("Unable to get Input Reader : "
					+ e);
		}
		this.createInputThread();
		this.messageQ = new OutPacketQueue();
		new OutQueueThread(this.messageQ, this.out).start();
		this.friendList = new FriendListImpl();

	}

	public void closeStream() throws IOException {
		this.getWriter().write("</stream:stream>");
		this.out.flush();
	}

	@Override
	public void addConnectionStateListener(ConnectionStateListener listener) {
		this.listeners.add(ConnectionStateListener.class, listener);

	}

	@Override
	public void addPresenceListener(PresenceListener e) {
		this.listeners.add(PresenceListener.class, e);

	}

	/**
	 * Get the connection to the server. If the call to this method succeed an
	 * open connection object to the server is returned Otherwise an
	 * ConnectionException is thrown.
	 * 
	 * @return
	 * @throws ConnectionException
	 */
	protected Connection getConnection() throws ConnectionException {
		Connection connection = this.wisigoth.getConnection();
		if (!connection.isConnected()) {
			try {
				connection.open();

			} catch (IOException e) {
				throw new ConnectionException(
						"Unable to connect to the server.", e);
			}

		}
		return connection;
	}

	private void createInputThread() {
		this.dispatcher = new MessageDispatcher();
		InputThread inThread = new InputThread(this);
		inThread.start();
	}

	@Override
	public void authenticate(String login, char[] password)
			throws AuthenticationException {
		try {
			String jabberID = this.getJabberId(login);
			/*
			 * this.getConnection().getOutputStream().write(
			 * ("<stream:stream from='" + jabberID + "' to='server'>") //
			 * ("<stream:stream from='tof@server/work' to='server'>")
			 * .getBytes()); this.getConnection().getOutputStream().flush();
			 */
			this
					.getWriter()
					.write(
							"<?xml version='1.0' encoding='UTF-8' ?><stream:stream from='"
									+ jabberID
									+ "' to='192.168.1.4' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>");
			this.out.flush();

		} catch (IOException e) {
			throw new WisigothRuntimeException("Failed to write opening stream"
					+ e);
		}

		this.connectionState = ConnectionState.ONLINE;
		this.setFriendList();
		this.myUser = new WisigothUserImpl(login + "@192.168.1.4");
		this.fireConnectionStateChanged();
	}

	private String getJabberId(String login) {
		return login + "@192.168.1.4";
	}

	private void fireConnectionStateChanged() {
		ConnectionEvent event = new ConnectionEvent(this);
		for (ConnectionStateListener listener : this.listeners
				.getListeners(ConnectionStateListener.class)) {
			listener.connectionStateChanged(event);
		}
	}

	@Override
	public void reconnect() throws AuthenticationException, ConnectionException {
		if (this.connectionState == ConnectionState.OFFLINE) {
			this.connectionState = ConnectionState.ONLINE;
			this.setPresence(DefaultPresence.AVAILABLE);
			this.fireConnectionStateChanged();
		}
	}

	@Override
	public void disconnect() throws ConnectionException {
		if (this.connectionState == ConnectionState.ONLINE) {
			this.connectionState = ConnectionState.OFFLINE;
			this.fireConnectionStateChanged();

		}
	}

	@Override
	public void setFriendList() {
		ContactImpl wiscarol = new ContactImpl(new WisigothUserImpl(
				"wiscarol@192.168.1.4"), DefaultPresence.AVAILABLE,
				AvatarImpl.POMPOM);
		ContactImpl java = new ContactImpl(new WisigothUserImpl(
				"java@192.168.1.4"), DefaultPresence.AVAILABLE, AvatarImpl.JESTER);
		ContactImpl tof = new ContactImpl(new WisigothUserImpl(
				"tof@192.168.1.4"), DefaultPresence.AVAILABLE,
				AvatarImpl.GRENADE);
		ContactImpl therese = new ContactImpl(new WisigothUserImpl(
				"therese@192.168.1.4"), DefaultPresence.AVAILABLE,
				AvatarImpl.GIRL);
		ContactImpl torjon = new ContactImpl(new WisigothUserImpl(
				"torjon@192.168.1.4"), DefaultPresence.AVAILABLE,
				AvatarImpl.FISHED);
		this.friendList.addContact(wiscarol);
		this.friendList.addContact(java);
		this.friendList.addContact(tof);
		this.friendList.addContact(therese);
		this.friendList.addContact(torjon);
	}

	@Override
	public Presence getPresence() {
		return this.presence;
	}

	@Override
	public Wisigoth getWisigoth() {
		return this.wisigoth;
	}

	@Override
	
	public ChatSession openChat(Contact contact) {
		ChatSession chatSession = new ChatSessionImpl(contact, this);
		this.dispatcher.addMessageConsumer(chatSession);
		return chatSession;
	}
	

	@Override
	public void closeChat(ChatSession chatSession) {
		this.dispatcher.removeMessageConsumer(chatSession);
	}

	@Override
	public void quitWisigoth() {
		if (this.connectionState == ConnectionState.ONLINE) {

			String presence = this.getPresence().toString();
			this.getWisigoth().getUserPreferences().storePresenceParameters(
					presence);
			try {
				this.closeStream();

				// this.wisigoth.getConnection().close();
			} catch (IOException e) {
				throw new WisigothRuntimeException(
						"Failed to close the Jabber Stream" + e);
			}
		}
		System.exit(0);

	}

	@Override
	public void removeConnectionStateListener(ConnectionStateListener listener) {
		this.listeners.remove(ConnectionStateListener.class, listener);

	}

	@Override
	public void removePresenceListener(PresenceListener e) {
		this.listeners.remove(PresenceListener.class, e);

	}



	@Override
	public void setPresence(Presence presence) {
		this.presence = presence;
		this.firePresenceChanged();

	}

	private void firePresenceChanged() {
		PresenceEvent event = new PresenceEvent(this);
		for (PresenceListener listener : this.listeners
				.getListeners(PresenceListener.class)) {
			listener.presenceChanged(event);
		}
	}

	@Override
	public ConnectionState getConnectionState() {
		return this.connectionState;

	}

	@Override
	public Conference openConference(Collection<Contact> contactList) {
		// TODO Auto-generated method stub
		throw new UnsupportedOperationException("Not implemented yet");
	}

	@Override
	public WisigothUser whoAmI() throws ConnectionException {
		if (this.myUser == null) {
			throw new ConnectionException("User not already connected");
		}
		return this.myUser;

	}

	@Override
	public void setConnectionState(ConnectionState ConnState) {
		this.connectionState = ConnState;

	}

	public Reader getReader() throws IOException {
		if (this.in == null) {
			this.in = new XercesReader(this.getConnection().getInputStream());
		}
		return this.in;
	}

	public Writer getWriter() throws IOException {
		if (this.out == null) {
			this.out = new BufferedWriter(new OutputStreamWriter(this
					.getConnection().getOutputStream()));
		}
		return this.out;
	}

	@SuppressWarnings("unchecked")
	@Override
	public void sendMessage(Message message) throws ConnectionException,
			MessageException {
		if (message.getMimeType() == MimeType.PLAIN_TEXT) {
			
			Packet packet = new Packet("message");
			packet.setTo(message.getTo().getNickName()
					+ "@192.168.1.4");
			packet.getChildren().add(new Packet("body",((ChatMessage) message).getText()));
			this.messageQ.push(packet);
					} else {
			throw new MessageException("Unknown message type: "
					+ message.getMimeType());
		}
		
		
		
		
		
		
		
	}

	public void setStreamID(String id) {
		this.streamID = id;
	}
	/**
	 * Give the message dispatcher associated to the current.
	 * @return {@link MessageDispatcher}
	 */
	public MessageDispatcher getDispatcher() {
		return this.dispatcher;
	}

	@Override
	/**
	 * Jabber implementation of the get Friend List method
	 */
	public FriendList getFriendList() throws FriendListException {
		if (this.friendList == null) {
			throw new FriendListException("No friend list available");
		}
		return this.friendList;
	}

	@Override
	public void setUnconsumedMessageConsumer(MessageConsumer consumer) {
		this.dispatcher.setUnconsumedMessageConsumer(consumer);
	}


}
